package com.qf.user.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.qf.common.core.config.ThreadPoolConfig;
import com.qf.common.core.entity.PointRule;
import com.qf.common.core.entity.Points;
import com.qf.common.core.entity.User;
import com.qf.common.core.exception.CustomerException;
import com.qf.common.core.utils.DateUtils;
import com.qf.common.core.utils.UserUtils;
import com.qf.common.minio.utils.MinioUtil;
import com.qf.common.redis.enums.RedisKeyEnum;
import com.qf.common.redis.utils.RedisUtil;
import com.qf.common.core.utils.JwtUtils;
import com.qf.common.sms.utils.AliSmsUtils;
import com.qf.commoon.oss.utils.OssUtils;
import com.qf.point.param.ContinueDayInfo;
import com.qf.point.service.PointRuleService;
import com.qf.point.service.PointsService;
import com.qf.user.mapper.UserMapper;
import com.qf.user.param.LoginParam;
import com.qf.user.param.RegisterParam;
import com.qf.user.service.UserService;
import com.qf.user.vo.MyVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author zdl
 * @date 2025/3/21
 */
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private UserMapper userMapper;

    @Resource
    private AliSmsUtils aliSmsUtils;
    @Resource
    private ThreadPoolConfig threadPoolExecutor;
    @Resource
    private PointRuleService pointRuleService;

    @Resource
    private MinioUtil minioUtil;

    @Resource
    private PointsService pointsService;

    @Override
    public String login(LoginParam loginParam) {
        //参数判断  可以通过注解的方式进行校验
/*        if (!StringUtils.hasLength(loginParam.getUsername())) {
            throw new CustomerException("3002","用户名不能为空");
        }
        if (!StringUtils.hasLength(loginParam.getPassword())) {
            throw new CustomerException("3003","密码不能为空");
        }*/
        User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getUsername, loginParam.getUsername()));
        if (user == null) {
            throw new CustomerException("3003", "用户名错误");
        }
        // 前端传过来的是明文密码，数据库存储的是MD5加密的密码，所以需要对前端传过来的密码进行加密再与数据库中的密码进行比较
        String md5Pwd = SecureUtil.md5(loginParam.getPassword());
        if (!(user.getPassword().equals(md5Pwd))) {
            throw new RuntimeException("密码错误");
        }
        //返回token  使用jwt作为token的生成方式
        Map<String, Object> map = new HashMap<>();
        map.put("uid", user.getId());
        return JwtUtils.createJwt(map);
    }

    @Override
    public int register(RegisterParam registerParam) {
        User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getUsername, registerParam.getUsername()));
        if (user != null) {
            throw new CustomerException("5001", "用户名已存在");
        }
        List<User> u = userMapper.selectList(Wrappers.lambdaQuery(User.class).eq(User::getPhone, registerParam.getPhone()));
        if (u != null && !u.isEmpty()) {
            throw new CustomerException("5001", "当前手机号已注册");
        }
        //从redis中取出验证码
        String key = String.format(RedisKeyEnum.KEY_SMS_CODE.getKey(), registerParam.getPhone());
        System.out.println("key:" + key);
        String cacheCode = redisUtil.getCacheObject(key);
        if (cacheCode == null) {
            throw new CustomerException("5002", "验证码已过期");
        }
        if (!cacheCode.equals(registerParam.getCode())) {
            throw new CustomerException("5003", "验证码错误");
        }
        //注册用户
        user = new User();
        user.setUsername(registerParam.getUsername());
        user.setPassword(SecureUtil.md5(registerParam.getPassword()));
        user.setPhone(registerParam.getPhone());
        return userMapper.insert(user) == 0 ? 0 : 1;
    }

    @Override
    public String verificationCode(String phone) {
        //生成一个四位随机纯数字作为验证码 不足 4 位时前面补零
        //String code = String.format("%04d", (int) (Math.random() * 10000));
        //使用hutool工具类生成随机数字
        String code = RandomUtil.randomNumbers(4);
        //异步发送验证码
        //threadPoolExecutor.taskExecutor().execute(() -> aliSmsUtils.sendSms(phone, code));
        //将验证码存入redis中，key为手机号，value为验证码  过期时间为300秒
        //validate:code:%s   字符串的格式化 %s表示占位符,对应字符串     %d对应数字
        String key = String.format(RedisKeyEnum.KEY_SMS_CODE.getKey(), phone);
        redisUtil.setCacheObject(key,
                code,
                RedisKeyEnum.KEY_SMS_CODE.getTimeout(),
                TimeUnit.SECONDS);
        return code;
    }

    @Override
    public MyVO myInfo(Integer uid) {
        User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .select(User::getId, User::getUsername, User::getPhone, User::getGender,User::getPoint, User::getAvatar)
                .eq(User::getId, uid));
        MyVO myVO = new MyVO();
        // 对象属性的拷贝(两个对象中的属性名和类型必须一致)
        BeanUtils.copyProperties(user, myVO);
        //todo 设置签到状态
       // myVO.setSignFlag(0);
        String yearMonth = DateUtils.date2Str(LocalDate.now(), "yyyyMM");
        //得到当月的总天数
        int totalDays = LocalDate.now().lengthOfMonth();
        //key为 sign:日期:用户id
        String key = String.format("sign:%s:%s", yearMonth, uid);
        //LocalDate.now().getDayOfMonth() 获取当天的日期作为偏移量  如2021-05-01 则偏移量为1
        int day = LocalDate.now().getDayOfMonth();
        boolean ret = redisUtil.getBit(key, day);
        myVO.setSignFlag(ret ? 1 : 0);
        return myVO;
    }

    @Override
    public String uploadAvatar(MultipartFile file) {
        //上传文件名
        String fileName = file.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        fileName = uuid + fileName.substring(fileName.lastIndexOf("."));
        String ossPath = "";
        try {
            ossPath = OssUtils.uploadFile2OSS(file.getInputStream(), fileName);
        } catch (IOException e) {
            throw new CustomerException("5004", "上传失败");
        }
        return ossPath;
    }

    @Override
    public String uploadAvatar2(MultipartFile file) {
        //上传文件名
        String fileName = file.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        fileName = uuid + fileName.substring(fileName.lastIndexOf("."));
        String ossPath = "";
        try {
            ossPath = minioUtil.upload(file.getInputStream(), fileName);
        } catch (IOException e) {
            throw new CustomerException("5004", "上传失败");
        }
        return ossPath;
    }

    @Override
    public void sign() {
        Integer uid = UserUtils.getUser();
        // 防止表单提交，本质使用了幂等性的思想
        // 幂等性: 同一操作，无论执行多少次，结果都一样。
        String s = String.format("sign:repeat:%s", uid);
        if (redisUtil.keyExist(s)){
            throw new CustomerException("5006", "稍后再试");
        }else {
            redisUtil.setCacheObject(s, "1", 10, TimeUnit.SECONDS);
        }
        String yearMonth = DateUtils.date2Str(LocalDate.now(), "yyyyMM");
        //得到当月的总天数
        int totalDays = LocalDate.now().lengthOfMonth();
        //key为 sign:日期:用户id
        String key = String.format("sign:%s:%s", yearMonth, uid);
        //LocalDate.now().getDayOfMonth() 获取当天的日期作为偏移量  如2021-05-01 则偏移量为1
        int day = LocalDate.now().getDayOfMonth();
        //若key不存在，则设置key的过期时间为当月的总天数-当天的日期+1，并设置当天的签到状态为true
        if (!redisUtil.keyExist(key)) {
            redisUtil.setBit(key, day, true);
            redisUtil.expire(key, totalDays - day + 1, TimeUnit.DAYS);
        } else {
            //若key存在，则判断当天是否已经签到过
            boolean ret = redisUtil.getBit(key, day);
            if (ret) {
                //已经签到过了
                throw new CustomerException("5005", "今日已签到");
            }
            //未签到过，则设置签到状态
            redisUtil.setBit(key, day, true);
        }


        //异步发送积分
        //如果签到成功，但是异步发送积分失败，如何处理？ 给出补偿机制(补偿表和定时任务)
        threadPoolExecutor.taskExecutor().execute(() -> {
            sendPoint(uid, key, day);
        });


    }

    public void sendPoint(Integer uid, String key, int day) {
        // 签到送积分
        // 根据积分规则查询送积分的信息
        //PointRule pointRule = pointRuleService.ruleInfo(1);
        // 查询全部的积分规则
        List<PointRule> pointRules = pointRuleService.ruleList();
        // 指定规则类型为1的规则
        PointRule pointRule = pointRules.stream()
                .filter(item -> item.getPointType().equals(1))
                .findFirst()
                .orElseThrow(null);
        if (pointRule != null) {
            Points points = new Points();
            points.setUid(uid);
            points.setPoint(pointRule.getPoint());
            points.setPointType(1);
            pointsService.addPoint(points);
        }
        // 连续签到判断  连续签到送积分
        Long v = redisUtil.bitfield(key, day, 1);
        String s = Long.toBinaryString(v); //将数字转成二进制字符串
        //判断连续
        int count = 0;  //连续签到天数
        for (int i = s.length() - 1; i >= 0; i--) {
            if (s.charAt(i) == '1') {
                //连续签到
                count++;
            } else {
                break;
            }
        }
        //PointRule pointRule1 = pointRuleService.ruleInfo(2);
        PointRule pointRule1 = pointRules.stream()
                .filter(item -> item.getPointType().equals(2))
                .findFirst()
                .orElseThrow(null);
        if (pointRule1 != null) {
            String days = pointRule1.getDays();       // [{"days":5,"ponit":10},{"days":10,"ponit":25}]
            // 将json字符串转成对象列表
            List<ContinueDayInfo> continueDayInfos = JSON.parseArray(days, ContinueDayInfo.class);
            int finalCount = count;
            ContinueDayInfo dayInfo = continueDayInfos.stream()
                    .filter(item -> item.getDays().equals(finalCount))
                    .findFirst()
                    .orElse(null);
            if (dayInfo != null) {
                // 连续签到送积分
                Points points1 = new Points();
                points1.setUid(uid);
                points1.setPoint(dayInfo.getPoint());
                points1.setPointType(2);
                pointsService.addPoint(points1);
            }
        }
    }

    @Override
    public User getInfo() {
        Integer uid = UserUtils.getUser();
        User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getId, uid));
        return user;
    }

    @Override
    public int update(User user) {
        int i = userMapper.update(user, Wrappers.lambdaQuery(User.class)
                .eq(User::getId, user.getId()));
        return i;
    }

    @Override
    public void updatePoint(Integer uid, Integer point) {
        userMapper.update(null, new LambdaUpdateWrapper<User>()
                .setSql("point = point + " + point)
                .eq(User::getId, uid));

    }

    @Override
    public User getUserById(Integer id) {

        return userMapper.selectById(id);
    }
}
